package edu.siu.math.egut.local;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Set;
import java.util.Map.Entry;

import edu.siu.math.egut.io.BadLiECommandException;
import edu.siu.math.egut.io.Constants;
import edu.siu.math.egut.io.LiE;
import edu.siu.math.egut.io.OutputPref;
import edu.siu.math.egut.local.Inequality.Symbol;
import edu.siu.math.egut.main.OutputStyle;
import edu.siu.math.egut.util.Algorithms;
import edu.siu.math.egut.util.BadSubstitutionException;
import edu.siu.math.egut.util.Group;
import edu.siu.math.egut.util.Monomial;
import edu.siu.math.egut.util.NotARootException;
import edu.siu.math.egut.util.Parabolic;
import edu.siu.math.egut.util.ParabolicReadException;
import edu.siu.math.egut.util.Polynomial;
import edu.siu.math.egut.util.PolynomialReadException;
import edu.siu.math.egut.util.Sunipotent;
import edu.siu.math.egut.util.Unipotent;
import edu.siu.math.egut.util.UnipotentReadException;
import edu.siu.math.egut.util.WeylWord;
import edu.siu.math.egut.util.WeylWordReadException;
import edu.siu.math.egut.util.Unipotent.PrefixRootPair;

/**
 * Represents a section integral.  
 * @author Joseph Hundley
 *
 */
public class SectionIntegral implements Serializable {

    /**
     * @author Joseph Hundley
     *
     */
    public static class UnipotentPrefixPair {
	private final Unipotent u;
	private final String prefix;

	/**
	 * @return
	 */
	public Unipotent getUnipotent() {
	    return u;
	}

	public String getPrefix() {
	    return prefix;
	}

	public UnipotentPrefixPair(final Unipotent u, final String prefix) {
	    super();
	    this.u = u;
	    this.prefix = prefix;
	}

    }

    /**
     * 
     */
    private static final long serialVersionUID = -4172558993735135568L;
    private static final String DIVIDER = "\\\\\\\\";
    public static final String PLACE_HOLDER_STRING = "PLACE_HOLDER";
    private final Domain d;
    private final Group g;
    private WeylWord w;
    private final Parabolic p;
    private Unipotent u;
    private Polynomial q;
    private Polynomial r;
    private Polynomial j;

    public SectionIntegral(Domain d, Group g, WeylWord w, Parabolic p,
	    Unipotent u, Polynomial q, Polynomial r, Polynomial j) {
	super();
	this.d = d;
	this.g = g;
	this.w = w;
	this.p = p;
	this.u = u;
	this.q = q;
	this.r = r;
	this.j = j;
    }

    public Unipotent getU() {
	return u;
    }

    public void setU(Unipotent u) {
	this.u = u;
    }

    public Domain getD() {
	return d;
    }

    public Group getG() {
	return g;
    }

    public WeylWord getW() {
	return w;
    }

    public Parabolic getP() {
	return p;
    }

    public Polynomial getQ() {
	return q;
    }

    public Polynomial getR() {
	return r;
    }

    public Polynomial getJ() {
	return j;
    }

    public static SectionIntegral load(String filename) {

	try {
	    // use buffering
	    InputStream file = new FileInputStream(filename);
	    InputStream buffer = new BufferedInputStream(file);
	    ObjectInput input = new ObjectInputStream(buffer);
	    try {
		// deserialize the List
		SectionIntegral answer = (SectionIntegral) input.readObject();
		/*
		 * ISSUE: answer will contain Monomials, Polynomials etc., which
		 * have not been entered into the stores for these objects.
		 */
		return answer.update();

	    } finally {
		input.close();
	    }
	} catch (ClassNotFoundException ex) {
	    System.err.println(ex);
	} catch (IOException ex) {
	    System.err.println(ex);
	}

	return null;

    }

    public SectionIntegral update() {
	return new SectionIntegral(d.update(), Group.getGroup(g.getRank()), w,
		p, u.update(), Polynomial.update(q), Polynomial.update(r),
		Polynomial.update(j));
	// the purpose of this method is to ensure that any Polynomials, etc.,
	// which are deserialized
	// are also added to the store. ergo, if a store for Weyl elements of
	// parabolics is implemented,
	// then one will need update methods for those classes as well.
    }

    public static SectionIntegral read(String string)
	    throws DomainReadException, InequalityReadException,
	    ParabolicReadException, UnipotentReadException, NotARootException,
	    WeylWordReadException, SectionIntegralReadException {

	String[] parts = string.split(DIVIDER);
	if (parts.length != 8) {
	    System.out.println("parts length = " + parts.length);
	    for (int i = 0; i < parts.length; i++)
		System.out.println("parts[" + i + "]=" + parts[i]);
	    throw new SectionIntegralReadException();
	}

	try {
	    // domain
	    System.out.println("reading domain " + parts[0]);
	    Domain d = Domain.read(parts[0]);
	    System.out.println("domain = " + d.toString());
	    // group
	    System.out.println("reading group " + parts[1]);
	    Group g = Group.getGroup(parts[1]);
	    System.out.println("g=" + g);

	    // Polynomial
	    Polynomial j = Polynomial.read(parts[2]);
	    // parabolic
	    System.out.println("Reading parabolic " + parts[3]);
	    Parabolic p = Parabolic.read(g, parts[3]);
	    System.out.println("p=" + p);
	    // Polynomial
	    Polynomial q = Polynomial.read(parts[4]);
	    // Polynomial
	    Polynomial r = Polynomial.read(parts[5]);
	    // Unipotent
	    System.out.println("Reading unipotent " + parts[6]);
	    UnipotentPrefixPair pair = Unipotent.read(parts[6]);
	    Unipotent u = pair.getUnipotent();
	    String pre = pair.getPrefix();
	    System.out.println("u=" + u);
	    // WeylWord
	    System.out.println("Reading Weyl word " + parts[7]);
	    WeylWord w = WeylWord.read(parts[7].trim(), g);
	    System.out.println("w=" + w);

	    return new SectionIntegral(d, g, w, p, u, q, r, j);
	} catch (PolynomialReadException e) {

	} catch (WeylWordReadException e) {
	    // generated catch block
	    e.printStackTrace();
	    throw e;
	} catch (NotARootException e) {
	    // generated catch block
	    e.printStackTrace();
	    throw e;
	} catch (UnipotentReadException e) {
	    // generated catch block
	    e.printStackTrace();
	    throw e;
	} catch (ParabolicReadException e) {
	    // generated catch block
	    e.printStackTrace();
	    throw e;
	} catch (InequalityReadException e) {
	    // generated catch block
	    e.printStackTrace();
	    throw e;
	} catch (DomainReadException e) {

	    e.printStackTrace();

	    throw e;
	}

	return null;

    }

    public String toString() {
	return toString(OutputStyle.SCREEN);
    }

    public String toString(OutputStyle sty) {
	StringBuilder sb = new StringBuilder();

	switch (sty) {
	case SCREEN:
	    sb.append(u.toString(true));
	    sb.append("\npsi(" + q.toString() + ")");
	    if (!r.equals(Polynomial.create(1)))
		sb.append("\tchi(" + r.toString() + ")");
	    if (!j.equals(Polynomial.create(1)))
		sb.append("\t|" + j.toString() + "|");
	    sb.append(d.constraintsToString(sty));
	    break;
	case TEX:
	    String initialString = "\\begin{aligned}" + "\\int_Df_{\\chi}(wu)";
	    sb.append(initialString);
	    int width = 10;
	    if (!q.equals(Polynomial.create(0))) {
		String qString = "\\psi\\left("
			+ q.toString(sty, Constants.PAGE_WIDTH - 5)
			+ "\\right)";
		if (width + qString.length() > Constants.PAGE_WIDTH) {
		    sb.append("\\\\\n");
		    width = 0;
		}
		sb.append(qString);
	    }
	    if (!r.equals(Polynomial.create(1))) {
		String rString = "\\chi\\left("
			+ r.toString(sty, Constants.PAGE_WIDTH - 5)
			+ "\\right)";
		if (width + rString.length() > Constants.PAGE_WIDTH) {
		    sb.append("\\\\\n");
		    width = 0;
		}
		sb.append(rString);
	    }
	    if (!j.equals(Polynomial.create(1))) {
		String jString = "\\left|"
			+ j.toString(sty, Constants.PAGE_WIDTH - 5)
			+ "\\right|";
		if (width + jString.length() > Constants.PAGE_WIDTH) {
		    sb.append("\\\\\n");
		    width = 0;
		}
		sb.append(jString);
	    }
	    sb.append("\\,du,\\\\\n");
	    sb.append("u=" + u.toString(OutputStyle.TEX) + ",\\\\\n");
	    sb.append("D=" + d.toString(OutputStyle.TEX) + ",\\\\\n");
	    sb.append("w=" + w.toString(OutputStyle.TEX) + "\\end{aligned}\n");

	}

	return sb.toString();
    }

    /**
     * The if si represents a section integral, then si.iwasawa0() represents
     * the integral obtained by restricting the argument of the rightmost
     * sunipotent which appears inside of the section in si to the ring of
     * integers of the ambient non-Archimedean local field. The section is then
     * invariant by this sunipotent, so si.iwasawa0() has one fewer sunipotent
     * appearing in the section.
     * 
     * @return new section integral with restricted domain
     */
    public SectionIntegral iwasawa0() {
	if (!u.getLast().getArgument().isASignedMonomial()) {
	    System.out
		    .println("Argument of last term is not a signed monomial.  Iwasawa blocked.");
	    return this;
	}

	Unipotent v = Unipotent.copyOf(u);
	v.remove(v.getNumberOfTerms() - 1);
	/*
	 * Suppose that the argument of the last Sunipotent is a single variable
	 * x, which does not appear anywhere else in the integrand, or in the
	 * bounds for the domain of integration. Then when we constrain this
	 * variable to be in the ring of integers we can just do the integral in
	 * this variable, getting one. In other words, in that situation, we can
	 * just drop the variable.
	 */

	Polynomial poly = u.getLast().getArgument();
	if (poly.isAVariable()) {

	    String x = poly.getVar();
	    if (d.doesNotConstrain(x)) {
		if (v.isIndependentOf(x) && q.isIndependentOf(x)
			&& r.isIndependentOf(x) && j.isIndependentOf(x)) {
		    ArrayList<String> newVars = d.getVariables();
		    newVars.remove(x);
		    Domain d1 = new Domain(newVars, d.getBounds());

		    return new SectionIntegral(d1, g, w, p, v, q, r, j);
		}
	    }

	}

	/*
	 * If the argument of the last Sunipotent is not a variable, or if it
	 * appears elsewhere in the integral, then it's not clear what will
	 * happen next, so the thing to do is just drop the last Sunipotent from
	 * the integral, and then add the new bound to the domain.
	 */
	return new SectionIntegral(d.addBound(Polynomial.create(1),
		Inequality.Symbol.GREATERTHANOREQUALTO, u.getLast()
			.getArgument()), g, w, p, v, q, r, j);
    }

    /**
     * The if si represents a section integral, then si.iwasawa1() represents
     * the integral obtained by restricting the argument of the rightmost
     * sunipotent which appears inside of the section in si to the complement of
     * the ring of integers in the ambient non-Archimedean local field. The
     * iwasawa decomposition for this rightmost sunipotent is plugged in and
     * conjugated to the right. So, si.iwasawa0() has one fewer sunipotent
     * appearing in the section.
     * 
     * @return new section integral with restricted domain
     * @throws BadSubstitutionException
     * @throws UncomputableJacobianException
     * @throws NotARootException
     * @throws IOException
     * @throws BadLiECommandException
     */
    public SectionIntegral iwasawa1() throws BadSubstitutionException,
	    UncomputableJacobianException, NotARootException, IOException,
	    BadLiECommandException {

	if (!u.getLast().getArgument().isASignedMonomial()) {
	    System.out
		    .println("Argument of last term is not a signed monomial.  Iwasawa blocked.");
	    return this;
	}
	Sunipotent suni = u.getLast();
	Polynomial arg = suni.getArgument();
	Unipotent v = Unipotent.copyOf(u);
	Polynomial j1 = j;
	Polynomial q1 = q;
	Polynomial r1 = r.times(character(suni));
	v.remove(v.getNumberOfTerms() - 1);

	SectionIntegral answer = new SectionIntegral(d.removeBound(
		Polynomial.create(1), Inequality.Symbol.GREATERTHANOREQUALTO,
		u.getLast().getArgument()).addBound(u.getLast().getArgument(),
		Inequality.Symbol.GREATERTHAN, Polynomial.create(1)), g, w, p,
		v, q1, r1, j1);

	for (int i = 0; i < v.getNumberOfTerms(); i++) {
	    if (v.stringOf(v.get(i)) != null) {
		// then we get x_r( y t^{-<r, a^vee>}) and make the substitution
		// y -> y t^{<r, a^vee>}
		;

		String x = v.stringOf(v.get(i));
		Polynomial sub = arg.raiseTo(
			(2 * u.getGroup().weylInvtInnProd(v.get(i).getRoot(),
				suni.getRoot()))
				/ u.getGroup().weylInvtInnProd(suni.getRoot(),
					suni.getRoot())).times(x);
		answer.replaceWithAnException(x, sub, i);

	    } else {
		// then we get x_r( p * t^{-<r, a^vee>})
		// (and don't make any change of variables in response).
		Sunipotent s1 = Sunipotent.create(v.get(i).getRoot(), v.getArg(
			i).times(
			arg.raiseTo((-2 * u.getGroup().weylInvtInnProd(
				v.get(i).getRoot(), suni.getRoot()))
				/ u.getGroup().weylInvtInnProd(suni.getRoot(),
					suni.getRoot()))));

		answer.getU().replace(i, s1);

	    }

	}
	answer.getU().rightMultiply(
		Sunipotent.create(g.negative(suni.getRoot()), suni
			.getArgument()));
	answer.getU().pushFarLeftAndRemove(v.getNumberOfTerms() - 1);
	answer.lReduce();
	answer.cleanUp();
	answer.makeChangesOfVariable();

	return answer;
    }

    /*
     * Consider a Sunipotent x_a(r). Assume that this Sunipotent appears
     * rightmost in an unramified section, and that |r| >1. Plugging in the
     * Iwasawa decomposition one has
     * 
     * f( w ... x_a(r) ) = f(w ... a^vee( r ) x_{-a}(r) ) = f( w a^\vee(r)
     * w^{-1} w ... x_{-a}(r) ). Now assume that f is in a representation
     * induced from \chi( \delta_P ) for some maximal parabolic P and character
     * \chi. Then we get a factor of \chi(\delta_p( w a^\vee w^{-1} ) )
     * appearing.
     * 
     * The purpose here is to compute \delta_P( w a^\vee w^{-1} ).
     * 
     * This is quite easy to do with LiE, and, for now at least, that seems the
     * best way to do it.
     */
    private Polynomial character(Sunipotent suni) throws IOException,
	    BadLiECommandException {

	LiE lie = new LiE();

	// formulae:
	// delta_P = sum( unipRad( <parabolic specifier for P> , g ))
	lie.exec("deltaP = sum(unipRad(" + p.getSpecifier() + ","
		+ g.toString() + "))", OutputPref.HIDE);

	// delta_P( w a^vee w^{-1} ) = a^n, where
	// n= delta_P*W_rt_action(w,g)*Cartan(g)*[a^vee],
	// where [a^vee] is the representation for a^vee as an
	// integral vector in terms of the basis of simple coroots.
	int n = Integer.parseInt(lie.exec("deltaP*W_rt_action("
		+ w.toString(OutputStyle.LIE) + "," + g.toString()
		+ ")*Cartan(" + g.toString() + ")*"
		+ corootString(suni.getRoot(), g), OutputPref.HIDE));
	return suni.getArgument().raiseTo(n);

    }

    private String corootString(int[] root, Group g2) {
	if (g2 == Group.G2) {
	    if (root[0] % 3 == 0)
		return ("[" + (root[0] / 3) + "," + root[1] + "]");
	    else
		return ("[" + root[0] + "," + (3 * root[1]) + "]");
	}
	if (g2 == Group.F4) {
	    if (root[0] % 2 == 0 && root[1] % 2 == 0)
		return "[" + (root[0] / 2) + "," + (root[1] / 2) + ","
			+ root[2] + "," + root[3] + "]";
	    else
		return "[" + (root[0]) + "," + (root[1]) + "," + (2 * root[2])
			+ "," + (2 * root[3]) + "]";
	}

	return edu.siu.math.egut.io.Algorithms.tupleToString(root);
    }

    private void replaceWithAnException(String x, Polynomial sub, int i)
	    throws BadSubstitutionException, UncomputableJacobianException {

	j = j.replace(x, sub).times(jacobian(x, sub));
	q = q.replace(x, sub);
	r = r.replace(x, sub);
	d.replace(x, sub);
	u.substituteWithAnException(x, sub, i);

    }

    /**
     * Save the SectionIntegral Object
     * 
     * @param filename
     * @param si
     */
    public static void save(final String filename, final SectionIntegral si) {
	try {
	    // use buffering
	    OutputStream file = new FileOutputStream(filename
		    + ((filename.endsWith(".ser") ? "" : ".ser")));
	    OutputStream buffer = new BufferedOutputStream(file);
	    ObjectOutput output = new ObjectOutputStream(buffer);
	    try {
		output.writeObject(si);
	    } finally {
		output.close();
	    }
	} catch (IOException ex) {
	    System.err.println(ex);
	}

    }

    /**
     * Move the sunipotent at position "from" to position "to." 
     * 
     * @param from
     * @param to
     */
    public void move(final int from, final int to) {
	u.move(from, to);

    }

    /**
     * Clean up the unipotent (combine terms with the same root).
     */
    public void cleanUp() {
	u.cleanUp();

    }

    
    /**
     * Simplify the unipotent by making changes of variable of the 
     * for u -> u-q when u is associated to a sunipotent of the form x_a( u+q )
     * or u-> -u+q when u is associated to a sunipotent of the form x_a( -u+q ).
     * @throws BadSubstitutionException if the substitution would take us outside 
     * if the set of Laurent polynomials
     */
    public void makeChangesOfVariable() throws BadSubstitutionException {

	for (int i = 0; i < u.getNumberOfTerms(); i++) {

	    /*
	     * get the String associated to Sunipotent i, (If Sunipotent i has
	     * no String, then stringOf() returns "null."
	     */
	    String s1 = u.stringOf(u.get(i));

	    if (s1 != null) {// i.e., if our Sunipotent is indeed associated to
			     // a variable...

		/*
		 * there are two cases that can be handled. Write arg = current
		 * argument of our Sunipotent. 1. arg = s1 + q, where q is
		 * independent of s1. In this case, the substitution s1 -> s1 -
		 * q is indicated. 2. arg = -s1+q. In this case, the
		 * substitution s1 -> (-s1+q) is indicated.
		 */
		// rewrite condition 1. above as
		// "q:= arg - s1 is independent of s1."
		if (u.get(i).getArgument().minus(Polynomial.create(s1))
			.isIndependentOf(s1)) {
		    // p1:= s1-q = 2 s1 - arg.
		    Polynomial p1 = Polynomial.create(s1).times(2).minus(
			    u.get(i).getArgument());

		    // s1 -> p1
		    q = q.replace(s1, p1); // in the Polynomial p which was
					   // passed to this method by the
					   // caller,
		    r = r.replace(s1, p1);
		    j = j.replace(s1, p1);
		    d.replace(s1, p1);
		    // Sunipotent suni1 = u.suniOf(s1);
		    u.substitute(s1, p1);// and in this Unipotent
		    // u.associate(suni1, s1);
		}
		// rewrite condition 2. above as q:= arg + s1 is independent of
		// s1."
		else if (u.get(i).getArgument().plus(Polynomial.create(s1))
			.isIndependentOf(s1)) {

		    // p1:= -s1+q = arg.
		    Polynomial p1 = u.get(i).getArgument();

		    // s1 -> p1

		    q = q.replace(s1, p1); // in the Polynomial p which was
					   // passed to this method by the
					   // caller,
		    r = r.replace(s1, p1);
		    j = j.replace(s1, p1);
		    d.replace(s1, p1);
		    u.substitute(s1, p1);// and in this Unipotent

		}

	    }// end if s1 not null

	}// end for each term...

    }

    /**
     * Simplify the unipotent by making changes of variable of the 
     * for u -> u-q when u is associated to a sunipotent of the form x_a( u+q )
     * or u-> -u+q when u is associated to a sunipotent of the form x_a( -u+q ).
     * @throws BadSubstitutionException if the substitution would take us outside 
     * if the set of Laurent polynomials
     */
    public void makeChangesOfVariable_new() throws BadSubstitutionException {
	
	for(String s1: u.getAssociations().values()){
	    Polynomial a = u.suniOf(s1).getArgument();


		/*
		 * there are two cases that can be handled. Write arg = current
		 * argument of our Sunipotent. 1. arg = s1 + q, where q is
		 * independent of s1. In this case, the substitution s1 -> s1 -
		 * q is indicated. 2. arg = -s1+q. In this case, the
		 * substitution s1 -> (-s1+q) is indicated.
		 */
		// rewrite condition 1. above as
		// "q:= arg - s1 is independent of s1."
		if (a.minus(Polynomial.create(s1))
			.isIndependentOf(s1)) {
		    // p1:= s1-q = 2 s1 - arg.
		    Polynomial p1 = Polynomial.create(s1).times(2).minus(
			    a);

		    // s1 -> p1
		    q = q.replace(s1, p1); // in the Polynomial p which was
					   // passed to this method by the
					   // caller,
		    r = r.replace(s1, p1);
		    j = j.replace(s1, p1);
		    d.replace(s1, p1);
		    // Sunipotent suni1 = u.suniOf(s1);
		    u.substitute(s1, p1);// and in this Unipotent
		    // u.associate(suni1, s1);
		}
		// rewrite condition 2. above as q:= arg + s1 is independent of
		// s1."
		else if (a.plus(Polynomial.create(s1))
			.isIndependentOf(s1)) {

		    // p1:= -s1+q = arg.
		    Polynomial p1 = a;

		    // s1 -> p1

		    q = q.replace(s1, p1); // in the Polynomial p which was
					   // passed to this method by the
					   // caller,
		    r = r.replace(s1, p1);
		    j = j.replace(s1, p1);
		    d.replace(s1, p1);
		    u.substitute(s1, p1);// and in this Unipotent

		}


	}// end for each term...

    }
    /**
     * Simplify the unipotent by making changes of variable of the 
     * for u -> u-q when u is associated to a sunipotent of the form x_a( u+q )
     * @throws BadSubstitutionException if the substitution would take us outside 
     * if the set of Laurent polynomials
     */
    public void makeChangesOfVariable_new_shorter() throws BadSubstitutionException {
	
	for(String s1: u.getAssociations().values()){
	    Polynomial a = u.suniOf(s1).getArgument();


		/*
		 * if a = s1 + q, where q is
		 * independent of s1, then the substitution s1 -> s1 -
		 * q is indicated. 
		 */
		// rewrite condition 1. above as
		// "q:= arg - s1 is independent of s1."
		if (a.minus(Polynomial.create(s1))
			.isIndependentOf(s1)) {
		    // p1:= s1-q = 2 s1 - arg.
		    Polynomial p1 = Polynomial.create(s1).times(2).minus(
			    a);

		    // s1 -> p1
		    q = q.replace(s1, p1); 
		    r = r.replace(s1, p1);
		    j = j.replace(s1, p1);
		    d.replace(s1, p1);
		    // Sunipotent suni1 = u.suniOf(s1);
		    u.substitute(s1, p1);// and in this Unipotent
		    // u.associate(suni1, s1);
		}
	}// end for each term...

    }

    
    /**
     * @param sty indicating the format for the output
     * @param name used as an equation label for tex output
     * @return String representation in chosen style
     */
    public String toString(final OutputStyle sty, final String name) {
	StringBuilder sb = new StringBuilder();

	switch (sty) {
	case TEX:
	    sb.append("\\begin{equation}\\label{" + name + "}\n");
	    sb.append(toString(sty));
	    sb.append("\\end{equation}");
	    break;
	default:
	    sb.append(toString(sty));
	}

	return sb.toString();
    }

    public SectionIntegral times(Sunipotent suni) {
	Unipotent u1 = Unipotent.times(getU(), suni);
	return new SectionIntegral(new Domain(d), g, w, p, u1, q, r, j);
    }

    public void lReduce() throws IOException, BadLiECommandException {
	u = edu.siu.math.egut.util.Algorithms.l_reduce(u, Algorithms.preimage(
		w, p.getRoots()));

    }

    public SectionIntegral replace(String variable, Polynomial q2)
	    throws BadSubstitutionException, UncomputableJacobianException {
	d.replace(variable, q2);
	u.substitute(variable, q2);
	u.clearZeros();
	q = q.replace(variable, q2);
	r = r.replace(variable, q2);
	j = j.replace(variable, q2).times(jacobian(variable, q2));
	return this;

    }

    private static Polynomial jacobian(String x, Polynomial q2)
	    throws UncomputableJacobianException {
	/*
	 * Desire is to produce the correct change of variable for the
	 * substitution x -> q2.
	 */
	if (q2.isIndependentOf(x))
	    return Polynomial.create(1);
	/*
	 * Such a case will not arise from any of the automated change of
	 * variables algorithms which are probably the primary concern.
	 * 
	 * Further, experience indicates that a substitution of this type is
	 * most often made in circumstances where the Jacobian, if computable,
	 * is not relevant. E.g., setting up an integral with a complicated
	 * unipotent argument by starting from a simpler one, or passage from a
	 * general case to a special case, or certain instances of "killing"
	 * when one can "tie two roots together."
	 * 
	 * In all these cases, no one is making an assertion that "this integral
	 * is equal to that integral by a change of variables." Therefore, one
	 * does not want the Jacobian.
	 * 
	 * At present, it seems best to require that, for any substitution of
	 * this type where the Jacobian is wanted, the user compute it
	 * "by hand."
	 */
	if (q2.degreeIn(x) == 1)
	    return q2.coeff(x);
	if (q2.degreeIn(x) == -1)
	    return q2.coeff(x).times(Monomial.create(x, -2));
	throw new UncomputableJacobianException(x + "->"
		+ q2.toString(OutputStyle.SCREEN));
    }

    public boolean hasVariable(String input) {
	return getAllVariables().contains(input);
    }

    /**
     * @param poly
     */
    public void setQ(Polynomial poly) {
	q = poly;

    }

    public static long getSerialversionuid() {
	return serialVersionUID;
    }

    public static String getDivider() {
	return DIVIDER;
    }

    public void setR(Polynomial r) {
	this.r = r;
    }

    public void setJ(Polynomial j) {
	this.j = j;
    }

    public void actBy(WeylWord w1, OutputPref op) throws WeylWordReadException, IOException,
	    BadSubstitutionException, UncomputableJacobianException,
	    NotARootException, BadLiECommandException {
	// compute new w,

	w = WeylWord.reduceByInverse(w, w1, g, op);

	// compute new u,
	u.actBy(w1.getWord());

	// make necessary changes of variable,
	/*
	 * This must be done in two stages. If a is a root we need to change u_a
	 * to +/-u_{w.a}. However, u_{w.a} may occur elsewhere in the integral.
	 * Therefore, we first change u_a to placeholder(u, w.a), and then go
	 * back and change placeholder(u,w.a) to u_{w.a}
	 * 
	 * (The sign of +/-u_{w.a} is determined by the structure constants and
	 * is such that w x_a(u_a) w^(-1) will become x_{w.a}(u_{w.a}). Note
	 * that we make such a change of variable in EVERY default variable
	 * which is found, regardless of whether or not x_a(u_a) is still
	 * present. There are two reasons for this: first, only updating those
	 * subscripts that we "need to" would lead to a mixture of updated an
	 * unupdated subscripts, and likely cause confusion, if not collisions.
	 * Second, it will facilitate notation which is uniform across various
	 * cases in a case-by-case analysis. )
	 */
	for (String x : this.getAllVariables()) {
	    PrefixRootPair prp = Unipotent.prefixRootPair(x);
	    if (prp != null && g.isRoot(prp.getRoot())) {

		changeDefaultToPlaceHolder(x, Polynomial.create(
			placeHolder(prp.getPrefix(), g.weylAction(w1.getWord(),
				prp.getRoot()))
		/*
		 * x is s default variable, formed from some String prefix and
		 * some root, which are recoverable. extract them. act by w on
		 * the root to get a new root. form the new default variable
		 * from the placeholder for that root and the variable.
		 */

		).times(g.weylSign(w1.getWord(), prp.getRoot()))// multiply by
								// the sign
								// corresponding
								// to action by
								// w on that
								// root.
		);
	    }
	}
	removePlaceHolders();

    }

    private void changeDefaultToPlaceHolder(String variable, Polynomial q2)
	    throws BadSubstitutionException, UncomputableJacobianException {
	d.replace(variable, q2);
	u.substituteSignedVariable(variable, q2);
	u.clearZeros();
	q = q.replace(variable, q2);
	r = r.replace(variable, q2);
	j = j.replace(variable, q2).times(jacobian(variable, q2));

    }

    private boolean isADefaultVariable(String x) {

	return Unipotent.DEFAULT_VARIABLE_PATTERN.matcher(x).matches();
    }

    private Set<String> getAllVariables() {
	Set<String> allVars = d.getAllVariables();
	allVars.addAll(u.getAllVariables());
	allVars.addAll(j.getAllVariables());
	allVars.addAll(q.getAllVariables());
	allVars.addAll(r.getAllVariables());

	return allVars;
    }

    private void removePlaceHolders() {
	Set<String> gav = getAllVariables();

	String[] allVars = new String[gav.size()];
	allVars = gav.toArray(allVars);
	for (String x : allVars)
	    if (x.endsWith(PLACE_HOLDER_STRING)) {
		replace(x, x.substring(0, x.length()
			- PLACE_HOLDER_STRING.length()));
	    }
    }

    private void removePlaceHoldersVerbose() {
	Set<String> gav = getAllVariables();

	String[] allVars = new String[gav.size()];
	allVars = gav.toArray(allVars);
	for (String x : allVars)
	    if (x.endsWith(PLACE_HOLDER_STRING)) {
		System.out.println(x
			+ " is a place holder.  Replace with "
			+ x.substring(0, x.length()
				- PLACE_HOLDER_STRING.length()));
		replace_old(x, x.substring(0, x.length()
			- PLACE_HOLDER_STRING.length()));
	    } else {
		System.out.println(x + " is not a place holder.");
	    }
    }

    private boolean isAPlaceHolder(Polynomial arg) {

	return arg.isAVariable() && arg.getVar().endsWith(PLACE_HOLDER_STRING);
    }

    /**
     * Replace one String variable with another throughout the integral
     * @param var which is replaced
     * @param string which replaces var
     */
    public void replace_old(final String var, final String string) {
	d.replace(var, string);
	q = q.old_replace(var, string);
	r = r.old_replace(var, string);
	j = j.old_replace(var, string);
	u.substitute(var, string);

    }
    
    
    /**
     * Replace one String variable with another throughout the integral
     * @param var which is replaced
     * @param string which replaces var
     */
    public void replace(String var, String string) {
	d.replace(var, string);
	q = q.replace(var, string);
	r = r.replace(var, string);
	j = j.replace(var, string);
	u.substitute(var, string);

    }

    private String placeHolder(String prefix, int[] root) {

	return Unipotent.defaultString(prefix, root) + PLACE_HOLDER_STRING;
    }

    private String defaultVariablePrefix(String var) {
	return var.substring(0, var.lastIndexOf('_'));
    }

    public void addNewSunis(ArrayList<Sunipotent> newSunipotents) {
	u.addRight(newSunipotents);
	d.addVariables(newSunipotents);

    }

    public void checkEnd() {
	if (canShortenU()) {
	    u.remove(u.getNumberOfTerms() - 1);
	    simplify();
	    checkEnd();
	}

    }

    private void simplify() {
	Set<String> allVarsSet = getAllVariables();
	String[] allVars = allVarsSet.toArray(new String[allVarsSet.size()]);
	for (int i = 0; i < allVars.length;) {
	    String x = allVars[i];
	    i++;
	    if (d.hasBound(1, Symbol.GREATERTHANOREQUALTO, x)
		    || d.hasBound(1, Symbol.GREATERTHANOREQUALTO, Polynomial
			    .create(x).times(-1))
		    || d.hasBound(-1, Symbol.GREATERTHANOREQUALTO, x)
		    || d.hasBound(-1, Symbol.GREATERTHANOREQUALTO, Polynomial
			    .create(x).times(-1))) {
		if (d.hasNoNonsimpleBoundsOn(x)) {
		    if (q.isIndependentOf(x) && r.isIndependentOf(x)
			    && j.isIndependentOf(x) && u.isIndependentOf(x)) {

			d.removeBound(1, Symbol.GREATERTHANOREQUALTO, x);
			d.removeBound(-1, Symbol.GREATERTHANOREQUALTO,
				Polynomial.create(x).times(-1));
			d.removeBound(1, Symbol.GREATERTHANOREQUALTO,
				Polynomial.create(x).times(-1));
			d.removeBound(-1, Symbol.GREATERTHANOREQUALTO, x);
			d.removeVariable(x);
			/*
			 * the counter i was incremented above. if we remove a
			 * variable, then this step is not correct, and so we
			 * undo it.
			 */
			i--;
		    }
		}
	    }

	}

    }

    private boolean canShortenU() {
	if (u.getNumberOfTerms() == 0)
	    return false;
	if (integersContain(u.getArg(u.getNumberOfTerms() - 1)))
	    return true;
	return false;
    }

    private boolean integersContain(Polynomial arg) {
	if (arg.equals(Polynomial.create(0))
		|| arg.equals(Polynomial.create(1))
		|| arg.equals(Polynomial.create(-1)))
	    return true;

	ArrayList<String> inO = new ArrayList<String>();
	ArrayList<String> notInO = new ArrayList<String>();
	for (Inequality i : d.getBounds()) {
	    if (i.implies(new Inequality(Polynomial.create(1),
		    Symbol.GREATERTHANOREQUALTO, arg)))
		return true;
	    if (i.getLHS().isConstant() && i.getRHS().isASignedVariable())
		inO.add(i.getRHS().getVar());
	    if ((i.getRHS().equals(Polynomial.create(1)) || i.getRHS().equals(
		    Polynomial.create(-1)))
		    && i.getLHS().isASignedVariable()
		    && i.getSymbol() == Symbol.GREATERTHAN)
		notInO.add(i.getLHS().getVar());

	}
	for (Entry<Monomial, Integer> e : arg.getTerms())
	    for (String x : e.getKey().getVariables()) {
		if (inO.contains(x) && e.getKey().degreeIn(x) < 0)
		    return false;
		if (notInO.contains(x) && e.getKey().degreeIn(x) > 0)
		    return false;
		if ((!inO.contains(x)) && (!notInO.contains(x)))
		    return false;
	    }
	return true;
    }

    public ArrayList<int[]> getPsiRoots() {
	ArrayList<int[]> answer = new ArrayList<int[]>();
	for (String x : q.getAllVariables())
	    if (u.suniOf(x) != null)
		answer.add(u.suniOf(x).getRoot());
	return answer;
    }

    public void clearZeros() {
	u.clearZeros();

    }

    /**
     * Checks matching of all attributes of two section integrals other than Q.
     * 
     * @param integral
     * @return
     */
    public boolean equalsUpToQ(SectionIntegral integral) {
	if (integral.d.equalsVerbose(d)) {
	    if (integral.g.getRank() == g.getRank()) {
		if (integral.j.equals(j)) {
		    if (integral.r.equals(r)) {
			if (integral.u.equals(u)) {
			    if (integral.p.equals(p)) {
				if (integral.w.equals(w))
				    return (true);
				else
				    say("different w." + w.toString()
					    + integral.w.toString());
			    } else
				say("different p." + p.toString()
					+ integral.p.toString());
			} else
			    say("different u." + u.toString()
				    + integral.u.toString());
		    } else
			say("different r." + r.toString()
				+ integral.r.toString());
		} else
		    say("different j." + j.toString() + integral.j.toString());
	    } else
		say("different g.\n" + g.toString() + integral.g.toString());
	} else
	    say("different d.\n" + d.toString(OutputStyle.SCREEN)
		    + integral.d.toString(OutputStyle.SCREEN));

	return false;
    }

    public static void say(String s) {
	System.out.println(s);
    }

    public void fix(String conjugateDummyVariable) {
	d.removeVariable(conjugateDummyVariable);

    }

    public void stateNumberOfAssociations() {
	u.stateNumberOfAssociations();
	
    }

}
